package tmall.framework.service.impl;

import org.springframework.util.Assert;
import tmall.framework.eobj.SegmentBufferEobj;
import tmall.framework.eobj.SegmentEobj;
import tmall.framework.exception.FrameworkExceptionDict;
import tmall.framework.exception.TException;
import tmall.framework.mapper.IdConfigMapper;
import tmall.framework.service.IdService;
import tmall.framework.utils.SpringContextUtil;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

//双buffer机制+数据库表+动态步长 支持每秒产生百万Id

public class SegmentsServiceImpl implements IdService {
    private IdConfigMapper idConfigMapper;
    //默认步长
    private static final int DEFAULT_STEP = 10000;
    //默认Key
    private static final String DEFAULT_KEY = "default_key";
    private static final String DEFAULT_DESCRIPTION = "default_description";
    private static final int DEFAULT_TIME = 15 * 60 * 1000;
    //如果步长设置的过大 会导致步长的浪费
    private static final int MAX_STEP = 1000000; //百万数据
    private static final long DEFAULT_MAX_ID = 10000000000l; //百万数据

    private Map<String, SegmentBufferEobj> cache = new HashMap();
    private static ExecutorService executor = Executors.newSingleThreadExecutor();

    public SegmentsServiceImpl() {
        idConfigMapper = SpringContextUtil.getBean(IdConfigMapper.class);
        // idConfigMapper

        Assert.notNull(idConfigMapper,"bean is not null");
        //初始化数据库表
        if (isInitDb.compareAndSet(false, true)) {
            //需要事务,做丢失更新
            List<SegmentEobj> segmentLs = idConfigMapper.queryAllSegment();
            if (segmentLs == null || segmentLs.size() == 0) {
                SegmentEobj segmentEobj = new SegmentEobj(DEFAULT_KEY, DEFAULT_MAX_ID, DEFAULT_STEP, DEFAULT_DESCRIPTION);
                segmentEobj.setUpdateTime(new Timestamp(System.currentTimeMillis()));
                idConfigMapper.addSegment(segmentEobj);
                segmentLs = new ArrayList<>();
                segmentLs.add(segmentEobj);
            }
           segmentLs.forEach(e->{
               SegmentBufferEobj segmentBufferEobj = new SegmentBufferEobj();
               segmentBufferEobj.setCurrent(e);
               cache.put(e.getName(), segmentBufferEobj);
           });
        }
    }


    @Override
    public long nextId() {
        return nextId(DEFAULT_KEY);
    }

    @Override
    public long nextId(String key) {
        SegmentBufferEobj segmentBuffer = cache.get(key);
        if (segmentBuffer == null) {
            throw new TException(FrameworkExceptionDict.NO_KEY_EXCEPTION);
        }
        initFirstBufferFromDb(segmentBuffer);
        return generateId(segmentBuffer);
    }

    private long generateId(SegmentBufferEobj segmentBuffer) {
        if (!segmentBuffer.isInit()) {
            initFirstBufferFromDb(segmentBuffer);
        }
        SegmentEobj segment = segmentBuffer.getCurrent();
        if (segmentBuffer.getCurrent() == null) {
            throw new TException(FrameworkExceptionDict.SEGMENT_NOT_INIT_EXCEPTION);
        }
        long id = segment.getNextId();
        updateSecondBuffer(segmentBuffer);
        if (id < segment.getMaxId()) {
            return id;
        }
        //当缓存1使用完后，切换缓存2，此时可能缓存2 还未初始化完毕
        for (; ; ) { //自旋 等待buffer加载完毕
            if (segmentBuffer.isSecondReady()) {
                synchronized (this) {
                    if (segmentBuffer.isSecondReady()) {
                        segmentBuffer.setSecondReady(false);
                        segmentBuffer.swapBuffer();
                        break;
                    }
                }
                //TODO 线程阻塞，等待通知
            }
        }
        segment = segmentBuffer.getCurrent();
        return segment.getNextId();
    }

    private void initFirstBufferFromDb(SegmentBufferEobj segmentBuffer) {
        if (!segmentBuffer.isInit()) {
            synchronized (segmentBuffer) {
                if (!segmentBuffer.isInit()) {
                    SegmentEobj segment = segmentBuffer.getCurrent();
                    idConfigMapper.updateMaxIdByKey(segment.getName());
                    segment.setValue(segment.getMaxId());
                    segment.setMaxId(segment.getMaxId()+segment.getStep());
                    segment.setMinStep(segment.getStep());
                    segmentBuffer.setInit(true);
                }
            }
        }

    }

    private void updateSecondBuffer(SegmentBufferEobj segmentBufferEobj) {
        SegmentEobj segment = segmentBufferEobj.getCurrent();
        if (!segmentBufferEobj.isSecondReady() && (segment.getIdle()) > 0.9 * segment.getMaxId()) {
            //当buffer用到了10% 就开始初始化buffer2 也就是当可用空间>90%就开始初始化
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    if (!segmentBufferEobj.isSecondReady() && (segment.getIdle()) > 0.9 * segment.getMaxId() && !segmentBufferEobj.getIsInitingSecondBuffer().compareAndSet(false, true)) {
                        try{
                            //step动态增长
                            long usingTime = System.currentTimeMillis() - segment.getUpdateTime().getTime();
                            int nextStep = segment.getStep();
                            if (usingTime < DEFAULT_TIME) {
                                if (segment.getStep() * 2 > MAX_STEP) {
                                    //do nothing
                                } else {
                                    nextStep = segment.getStep() * 2;
                                }
                            } else if (usingTime < DEFAULT_TIME) {

                            } else {
                                nextStep = nextStep / 2 > segment.getMinStep() ? nextStep / 2 : nextStep;
                            }
                            SegmentEobj temp = new SegmentEobj();
                            temp.setName(segment.getName());
                            temp.setStep(nextStep);
                            temp.setMaxId(segment.getMaxId());
                            temp.setUpdateTime(new Timestamp(System.currentTimeMillis()));
                            idConfigMapper.updateSegmentByKey(temp);
                            SegmentEobj segmentEobj = idConfigMapper.getSegmentByKey(temp.getName());
                            segmentBufferEobj.setSecond(segmentEobj);
                            segmentBufferEobj.setSecondReady(true);
                        }finally {
                            segmentBufferEobj.setIsInitingSecondBuffer(new AtomicBoolean(false));
                        }
                    }
                }
            });
        }
    }

}
